package com.ds.infra.ibatis.binding;

import com.ds.infra.ibatis.annotation.Delete;
import com.ds.infra.ibatis.annotation.Insert;
import com.ds.infra.ibatis.annotation.Select;
import com.ds.infra.ibatis.annotation.Update;
import com.ds.infra.ibatis.mapping.MappedStatement;
import com.ds.infra.ibatis.mapping.SqlCommandType;
import com.ds.infra.ibatis.handler.ParameterTokenParser;
import com.ds.infra.ibatis.session.Configuration;
import com.ds.infra.ibatis.session.SqlSession;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 作用：解析Mapper接口中的注解，并创建对应的MappedStatement对象
 *
 * @author WeiShaoying
 */
@SuppressWarnings("all")
public class MapperRegistry {

    // 配置对象，用于获取相关信息
    private final Configuration configuration;

    // 存储已知的Mapper接口及其对应的代理工厂
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

    public MapperRegistry(Configuration config) {
        this.configuration = config;
    }

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        //  获取对应的代理工厂
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            // 使用代理工厂创建Mapper的实例
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new RuntimeException("Error getting mapper instance. Cause: " + e, e);
        }
    }

    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
            }
            // new代理工厂，并存入knownMappers中
            knownMappers.put(type, new MapperProxyFactory<>(type));

            // 解析Mapper接口的注解
            parseMapperAnnotations(type);
        }
    }

    private void parseMapperAnnotations(Class<?> type) {
        // 使用getMethods()获取Mapper接口的所有公共方法
        for (Method method : type.getMethods()) {
            try {
                // 先创建MappedStatement再检查，确保所有方法都被处理
                if (method.isAnnotationPresent(Select.class)) {
                    parseStatement(type, method, SqlCommandType.SELECT);
                } else if (method.isAnnotationPresent(Insert.class)) {
                    parseStatement(type, method, SqlCommandType.INSERT);
                } else if (method.isAnnotationPresent(Update.class)) {
                    parseStatement(type, method, SqlCommandType.UPDATE);
                } else if (method.isAnnotationPresent(Delete.class)) {
                    parseStatement(type, method, SqlCommandType.DELETE);
                }
            } catch (Exception e) {
                throw new RuntimeException("Error parsing method " + method.getName() + " in mapper " + type.getName(), e);
            }
        }
    }

    private void parseStatement(Class<?> mapperInterface, Method method, SqlCommandType sqlCommandType) {
        try {
            String sql = getSqlFromAnnotation(method, sqlCommandType);
            String statementId = mapperInterface.getName() + "." + method.getName();

            // 总是创建新的MappedStatement，不检查是否已存在
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(statementId);
            mappedStatement.setSql(sql);
            mappedStatement.setProcessedSql(ParameterTokenParser.replacePlaceholders(sql));
            mappedStatement.setParameterNames(ParameterTokenParser.parseParameterNames(sql));
            mappedStatement.setSqlCommandType(sqlCommandType);
            mappedStatement.setResultType(getReturnType(method));

            configuration.addMappedStatement(mappedStatement);
        } catch (Exception e) {
            throw new RuntimeException("Error creating MappedStatement for method " + method.getName(), e);
        }
    }

    private String getSqlFromAnnotation(Method method, SqlCommandType sqlCommandType) {
        switch (sqlCommandType) {
            case SELECT:
                return method.getAnnotation(Select.class).value();
            case INSERT:
                return method.getAnnotation(Insert.class).value();
            case UPDATE:
                return method.getAnnotation(Update.class).value();
            case DELETE:
                return method.getAnnotation(Delete.class).value();
            default:
                throw new RuntimeException("Unknown SQL command type: " + sqlCommandType);
        }
    }

    private Class<?> getReturnType(Method method) {
        Class<?> returnType = method.getReturnType();
        if (List.class.isAssignableFrom(returnType)) {
            Type genericReturnType = method.getGenericReturnType();
            if (genericReturnType instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                if (actualTypeArguments != null && actualTypeArguments.length > 0) {
                    return (Class<?>) actualTypeArguments[0];
                }
            }
        }
        return returnType;
    }

    public <T> boolean hasMapper(Class<T> type) {
        return knownMappers.containsKey(type);
    }
}
